/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.painting.views;

import android.view.View;
import android.view.ViewGroup;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.ApplicationData;
import com.hercules.android.receiver.model.commands.XRECallCommand;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.objects.XREView;
import com.hercules.android.receiver.painting.PaintersUtil;
import com.hercules.android.receiver.painting.XREObjectPainter;
import com.hercules.android.receiver.painting.XREObjectPainterException;

import java.util.List;
import java.util.Map;

/**
 * <p>
 * This class extends XREObjectPainter and have a generic parameter as the view type. This class manages the android
 * view hierarchy according to the XREView hierarchy, kept their structure the same. When a XREView is added/removed or
 * a child is added/removed, the corresponding change is made to the view.
 * </p>
 * <p>
 * AndroidAssembly1 this class was rewritten due to a large number of bugs
 * <p>
 * <b> Thread safety: </b> It's mutable and not thread safe.
 * </p>
 *
 * @author linwe, EveningSun
 * @author 7realm
 * @version 1.0
 */
@SuppressWarnings("boxing")
public class XREViewPainter extends XREObjectPainter {
    /**
     * Default empty constructor.
     */
    public XREViewPainter() {
        // empty
    }

    /**
     * Paint the specified view when the view is created with NEW command. This method is expected to be called by
     * paintView method.
     *
     * @param receiver the AndroidReceiver instance
     * @param xreView the XREView to paint
     * @param command the command triggering the repaint
     * @throws IllegalArgumentException if any argument is null
     * @throws XREObjectPainterException if any other error occurs
     */
    @Override
    protected void paintViewOnNew(final AndroidReceiver receiver, final XREView xreView, XRECommand command)
        throws XREObjectPainterException {
        PaintersUtil.checkNull(receiver, "receiver");
        PaintersUtil.checkNull(xreView, "view");
        PaintersUtil.checkNull(command, "command");

        // create new android view
        final AndroidXREView androidView = new AndroidXREView(receiver.getRootView().getContext());

        // set receiver
        androidView.setReceiver(receiver);

        // attach view
        androidView.attachView(xreView);

        // add created view to receiver.applicationData.viewsXREIdToView map
        getViewsXREIdToView(receiver).put(xreView.getId(), androidView);

        // add to parent
        androidView.updateLayer();
        receiver.getRootView().invalidate();
    }

    /**
     * <p>
     * Paint the specified view when the view is deleted with DELETE command. This method is expected to be called by
     * paintView method.
     * </p>
     *
     * @param receiver the AndroidReceiver instance
     * @param view the XREView to paint
     * @param command the command triggering the repaint
     * @throws IllegalArgumentException if any argument is null
     * @throws XREObjectPainterException if any other error occurs
     */
    @Override
    protected void paintViewOnDelete(final AndroidReceiver receiver, XREView view, XRECommand command)
        throws XREObjectPainterException {
        PaintersUtil.checkNull(receiver, "receiver");
        PaintersUtil.checkNull(view, "view");
        PaintersUtil.checkNull(command, "command");

        // get current view from receiver.applicationData.viewsXREIdToView using current view id
        final AndroidXREView androidView = getViewById(receiver, view.getId(), AndroidXREView.class);

        // get parent from receiver.applicationData.viewsXREIdToView using parent id
        final ViewGroup parentGroup = (ViewGroup) androidView.getParent();
        
        // detach resource
        androidView.detachResource();

        // remove current view from receiver.applicationData.viewsXREIdToView
        getViewsXREIdToView(receiver).remove(view.getId());

        // remove the view from hierarchy
        if (parentGroup != null) {
	        parentGroup.removeView(androidView);
	
	        // clear animation
	        androidView.clearAnimation();
        }
        
        //if (androidView.isLeaf()) {// || androidView.getXREView().isPlayingAnimation()
        	final List<XREView> allChildren = view.getAllChildren();
        	for (XREView childView : allChildren) {
        		ViewGroup parent = (ViewGroup) childView.getAndroidXREView().getParent();
        		if (parent != null) {
	        		parent.removeView(childView.getAndroidXREView());
	        		childView.getAndroidXREView().clearAnimation();
        		}
        		getViewsXREIdToView(receiver).remove(childView.getId());
            }
        //}
        
        // invalidate whole hierarchy because of clipping
        receiver.getRootView().invalidate();
    }

    /**
     * <p>
     * Paint the specified view when the view hierarchy is not changed by the incoming command. This method is expected
     * to be called by paintView method.
     * </p>
     *
     * @param receiver the AndroidReceiver instance
     * @param view the XREView to paint
     * @param command the command triggering the repaint
     * @throws IllegalArgumentException if any argument is null
     * @throws XREObjectPainterException if any other error occurs
     */
    @Override
    protected void paintViewOnUpdate(AndroidReceiver receiver, XREView view, XRECommand command)
        throws XREObjectPainterException {
        PaintersUtil.checkNull(receiver, "receiver");
        PaintersUtil.checkNull(view, "view");
        PaintersUtil.checkNull(command, "command");

        // get current view from receiver.applicationData.viewsXREIdToView using current view id
        AndroidXREView androidView = getViewById(receiver, view.getId(), AndroidXREView.class);

        // handle command
        androidView.handleCommand(command);
    }

    /**
     * <p>
     * Paint the specified view when a child is added to the view with CALL command. This method is expected to be
     * called by paintView method.
     * </p>
     *
     * @param receiver the AndroidReceiver instance
     * @param view the XREView to paint
     * @param command the command triggering the repaint
     * @throws IllegalArgumentException if any argument is null
     * @throws XREObjectPainterException if any other error occurs
     */
    @Override
    protected void paintViewOnAddChild(AndroidReceiver receiver, XREView view, XRECommand command)
        throws XREObjectPainterException {
        PaintersUtil.checkNull(receiver, "receiver");
        PaintersUtil.checkNull(view, "view");
        PaintersUtil.checkNull(command, "command");

        // lookup the child view with command.params[0] in receiver.applicationData.viewsXREIdToView command.params[0]
        final AndroidXREView childView = getViewsXREIdToView(receiver).get(getCommandParam(command, 0));

        childView.updateLayer();
        if (childView.isLeaf()) {
        	final List<XREView> allChildren = view.getAllChildren();
        	for (XREView childXREView : allChildren) {
	        	childXREView.getAndroidXREView().updateLayer();
            }
        }
        view.getAndroidXREView().invalidate();
    }

    /**
     * <p>
     * Paint the specified view when a child is removed to the view with CALL command. This method is expected to be
     * called by paintView method.
     * </p>
     *
     * @param receiver the AndroidReceiver instance
     * @param view the XREView to paint
     * @param command the command triggering the repaint
     * @throws IllegalArgumentException if any argument is null
     * @throws XREObjectPainterException if any other error occurs
     */
    @Override
    protected void paintViewOnRemoveChild(final AndroidReceiver receiver, XREView view, XRECommand command)
        throws XREObjectPainterException {
        PaintersUtil.checkNull(receiver, "receiver");
        PaintersUtil.checkNull(view, "view");
        PaintersUtil.checkNull(command, "command");

        final ViewGroup parentGroup = view.getAndroidXREView();
        
        // lookup the child view with command.params[0] in receiver.applicationData.viewsXREIdToView command.params[0]
        final AndroidXREView childView = getViewsXREIdToView(receiver).get(getCommandParam(command, 0));


        // remove the view from hierarchy
        parentGroup.removeView(childView);

        // clear animation
        childView.clearAnimation();
        
        if (childView.isLeaf()) {
        	final List<XREView> allChildren = view.getAllChildren();
        	for (XREView childXREView : allChildren) {
        		ViewGroup parent = (ViewGroup) childXREView.getAndroidXREView().getParent();
        		if (parent != null) {
	        		parent.removeView(childXREView.getAndroidXREView());
	        		childXREView.getAndroidXREView().clearAnimation();
        		}
            }
        }
        
        // invalidate whole hierarchy because of clipping
        receiver.getRootView().invalidate();
    }

    /**
     * <p>
     * Paint the specified view when a child is removed to the view with CALL command. This method is expected to be
     * called by paintView method.
     * </p>
     *
     * @param receiver the AndroidReceiver instance
     * @param view the XREView to paint
     * @param command the command triggering the repaint
     * @throws IllegalArgumentException if any argument is null
     * @throws XREObjectPainterException if any other error occurs
     */
    @Override
    protected void paintViewOnSetChildIndex(AndroidReceiver receiver, XREView view, XRECommand command)
        throws XREObjectPainterException {
        PaintersUtil.checkNull(receiver, "receiver");
        PaintersUtil.checkNull(view, "view");
        PaintersUtil.checkNull(command, "command");

        // lookup the child view with command.params[0] in receiver.applicationData.viewsXREIdToView command.params[0]
        final AndroidXREView childView = getViewsXREIdToView(receiver).get(getCommandParam(command, 0));

        childView.updateLayer();
        view.getAndroidXREView().invalidate();
    }

    /**
     * <p>
     * Get the viewsXREIdToView map from the receiver.
     * </p>
     *
     * @param receiver the android receiver
     * @return the viewsXREIdToView map from the receiver
     * @throws XREObjectPainterException if the applicationData or viewsXREIdToView is null
     */
    private static Map<Integer, AndroidXREView> getViewsXREIdToView(AndroidReceiver receiver)
        throws XREObjectPainterException {
        ApplicationData applicationData = PaintersUtil.required(receiver.getApplicationData(), "applicationData");
        return PaintersUtil.required(applicationData.getViewsXREIdToView(), "viewsXREIdToView");
    }

    /**
     * <p>
     * Get a view according to the given view id from the data contained in the the given receiver.
     * </p>
     *
     * @param <T> the expected type of the view
     * @param receiver the android receiver
     * @param id the id of the view
     * @param type the expected type of the view
     * @return the retrieved view
     * @throws XREObjectPainterException if the any data is missing in the receiver or the view id cannot be found or
     * the view is not the expected type
     */
    private static <T extends View> T getViewById(AndroidReceiver receiver, int id, Class<T> type)
        throws XREObjectPainterException {
        try {
            Object view = PaintersUtil.required(getViewsXREIdToView(receiver).get(id), "view with the id " + id);
            return type.cast(view);
        } catch (ClassCastException e) {
            throw new XREObjectPainterException("The view with the id " + id + " should be type " + type, e);
        }
    }

    /**
     * <p>
     * Convert the given command to call command and get the first parameter.
     * </p>
     * <p>
     * AndroidAssembly2 changed to get params by index
     *
     * @param command the command
     * @param index the index of parameter
     * @return the first parameter of the command
     * @throws XREObjectPainterException if the command is not an instance of XRECallCommand or if the command does not
     * contains any parameter
     */
    private static Object getCommandParam(XRECommand command, int index) throws XREObjectPainterException {
        try {
            XRECallCommand callCommand = (XRECallCommand) command;
            if (callCommand.getParams() == null || callCommand.getParams().isEmpty()) {
                throw new XREObjectPainterException("The params of command " + callCommand
                    + " cannot be null or empty.");
            }
            if (callCommand.getParams().size() < index + 1) {
                throw new XREObjectPainterException("The expected parameters at '" + index + "' but there are only '"
                    + callCommand.getParams().size() + "' parameters in command.");
            }
            return callCommand.getParams().get(index);
        } catch (ClassCastException e) {
            throw new XREObjectPainterException("The command " + command + " is not an instance of "
                + XRECallCommand.class);
        }
    }
}
